package com.zerro.litez.compiler.processor.parser;

import com.zerro.litez.annotations.entity.AutoMigrationSign;
import com.zerro.litez.annotations.entity.ColumnDefine;
import com.zerro.litez.annotations.entity.PrimaryKey;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import java.util.*;

public class FieldInfo {

    public enum Access {
        byField,
        byMethod
    }

    private final String name;
    private String javaType;

    private Map<String, AnnotationInfo> annotationInfoMap = new HashMap<>();
    private Map.Entry<String, Access> getter = null;
    private Map.Entry<String, Access> setter = null;

    String tableSign;
    String sign = "";
    boolean tableSignAsPrefix;
    String entityName;


    int constructorIndex = -1;

    private static final Map<String, String> typeMap = new HashMap<>();
    private static final List<String> originTypeList = new ArrayList<>();
    private static final List<String> entityTypeList = new ArrayList<>();

    static {
        typeMap.put(String.class.getName(), "text");
        typeMap.put(Integer.class.getName(), "integer");
        typeMap.put(Long.class.getName(), "bigint");
        typeMap.put(Short.class.getName(), "smallint");
        typeMap.put(Byte.class.getName(), "tinyint");
        typeMap.put(Boolean.class.getName(), "boolean");
        typeMap.put(Float.class.getName(), "float");
        typeMap.put(Double.class.getName(), "double");

        originTypeList.add(int.class.getName());
        originTypeList.add(long.class.getName());
        originTypeList.add(short.class.getName());
        originTypeList.add(byte.class.getName());
        originTypeList.add(boolean.class.getName());
        originTypeList.add(float.class.getName());
        originTypeList.add(double.class.getName());
    }


    private String specialSqlType = null;



    TypeParser typeParser;

    FieldInfo(VariableElement field, String entityName, String tableSign, boolean tableSignAsPrefix) {


        name = field.getSimpleName().toString();
        javaType = field.asType().toString();
        this.tableSign = tableSign;
        this.tableSignAsPrefix = tableSignAsPrefix;


        this.entityName = entityName;
        if (field.getModifiers().contains(Modifier.PUBLIC)) {
            getter = new AbstractMap.SimpleEntry<>(name, Access.byField);
            setter = new AbstractMap.SimpleEntry<>(name, Access.byField);
        }
        typeParser = new TypeParser(field, entityName);
    }

    public void setAnnotationInfoMap(Map<String, AnnotationInfo> annotationInfoMap) {
        this.annotationInfoMap = annotationInfoMap;
    }

    public void checkSign() {
        AnnotationInfo signAnno = getAnnotationInfo(AutoMigrationSign.class);
        if (signAnno == null || signAnno.getValue().isEmpty()) {
            throw new RuntimeException("必须为所有字段添加 @AutoMigrationSign 注解\n" +
                    "位于 Entity<" + entityName + ">,Field<" + name + ">");
        } else {
            if (tableSignAsPrefix) {
                sign = tableSign + "_" + signAnno.getValue();
            } else {
                sign = signAnno.getValue();
            }
        }
    }

    public void setGetter(List<ExecutableElement> methods) {
        if (getter == null) {
            for (ExecutableElement method : methods) {
                String first = String.valueOf(name.charAt(0));
                String Name = name.replaceFirst(first, first.toUpperCase());
                String getter = "get" + Name;
                if (Objects.equals(javaType, Boolean.class.getName())) {
                    getter = "is" + Name;
                }
                String returns = method.getReturnType().toString();
                if (isArrayType()) {
                    returns = returns.replace("[]", "");
                }
                if (getter.equals(method.getSimpleName().toString()) &&
                        (returns.equals(javaType) ||
                                returns.equals(String.join(".", getEnumFullName())) ||
                                (isEntityType() && returns.equals(getEntityTypeParser().getParsingElement()
                                        .getQualifiedName().toString()))
                        )) {
                    this.getter = new AbstractMap.SimpleEntry<>(getter, Access.byMethod);
                    break;
                }

            }
        }
    }

    public void setConstructorIndex(ExecutableElement constructor) {
        if (constructor == null) {
            return;
        }
        List<? extends VariableElement> parameters = constructor.getParameters();
        for (int i = 0; i < parameters.size(); i++) {
            VariableElement param = parameters.get(i);
            String paramType = param.asType().toString();

            if ((paramType.equals(javaType) ||
                    paramType.equals(getEnumFullName()[0] + '.' + getEnumFullName()[1]) ||
                    (isEntityType() && paramType.equals(getEntityTypeParser().entityName)))
                    && name.equals(param.getSimpleName().toString())) {
                constructorIndex = i;
            }
        }
    }

    public void setSetter(List<ExecutableElement> methods) {
        if (setter == null) {
            for (ExecutableElement method : methods) {
                String first = String.valueOf(name.charAt(0));
                String setter = ("set" + name.replaceFirst(first, first.toUpperCase()) + "(" + javaType + ")")
                        .split("\\(")[0];
                if (method.getParameters().size() == 1) {
                    String paramType = method.getParameters().get(0).asType().toString();
                    if (isArrayType()) {
                        paramType = paramType.replace("[]", "");
                    }
                    if (setter.equals(method.getSimpleName().toString()) &&
                            (paramType.equals(javaType) ||
                                    paramType.equals(String.join(".", getEnumFullName())) ||
                                    (isEntityType() && paramType.equals(getEntityTypeParser().getParsingElement()
                                                    .getQualifiedName().toString())))) {
                        this.setter = new AbstractMap.SimpleEntry<>(setter, Access.byMethod);
                        break;
                    }
                }
            }
        }
    }


    public String getGetter() {
        if (getter == null) {
            return null;
        }

        if (getter.getValue() == Access.byField) {
            return getter.getKey();
        }
        return getter.getKey() + "()";
    }


    public String getSetter(String value) {
        if (setter == null) {
            return null;
        }


        if (setter.getValue() == Access.byField) {
            return setter.getKey() + " = " + value;
        }

        if (setter.getValue() == Access.byMethod) {
            return setter.getKey() + "(" + value + ")";
        }

        return null;

    }

    public boolean hasGetter() {
        return getter != null;
    }

    public boolean hasSetter() {
        return setter != null;
    }


    public AnnotationInfo getAnnotationInfo(Class klass) {
        return annotationInfoMap.get(klass.getSimpleName());
    }

    public String getSqlType() {
        if (specialSqlType != null) {
            return specialSqlType;
        }
        if (isEnumType()) {
            return typeMap.get(String.class.getName());
        }
        if (isEntityType()) {
            return getEntityTypePk().getSqlType();
        }
        return typeMap.get(javaType);
    }

    public String getSqlType(Class<?> javaType) {
        return typeMap.get(javaType.getName());
    }

    public String getJavaType() {
        return javaType;
    }

    public String getRealType() {
        return typeParser.realTypeStr;
    }


    public String getColumnName() {
        AnnotationInfo define = getAnnotationInfo(ColumnDefine.class);
        String sqlName = getName();
        if (define != null) {
            sqlName = define.getValue("name");
        }
        return sqlName.isEmpty() ? getName() : sqlName;
    }

    public String getSign() {
        return sign;
    }

    public String getDefaultValue() {
        AnnotationInfo define = getAnnotationInfo(ColumnDefine.class);
        String value = "";
        if (define != null) {
            value = define.getValue("defaultValue");
        }
        return value;
    }

    public boolean isNullable() {
        if (getAnnotationInfo(PrimaryKey.class) != null) {
            return false;
        }
        AnnotationInfo define = getAnnotationInfo(ColumnDefine.class);
        if (define != null) {
            return !Boolean.parseBoolean(define.getValue("notNull"));
        }
        return true;
    }

    public String getName() {
        return name;
    }

    public String getEntityName() {
        return entityName;
    }



    @Override
    public String toString() {
        return name;
    }

    public String[] getEnumFullName() {
        return typeParser.getEnumFullName();
    }

    public static void addEntityType(TypeElement entity) {
        entityTypeList.add(entity.getQualifiedName().toString());
    }

    public EntityClassParser getEntityTypeParser() {
        return typeParser.getEntityTypeParser();
    }

    public FieldInfo getEntityTypePk() {
        return typeParser.getEntityTypePk();
    }

    public boolean isSimpleType() {
        return typeParser.isSimpleType();
    }
    public boolean isEntityType() {
        return typeParser.isEntityType();
    }


    public boolean isEnumType() {
        return typeParser.isEnumType();
    }

    public boolean isArrayType() {
        return typeParser.isArray();
    }

    public boolean isContainer() {
        return typeParser.isContainer();
    }

    public String getContainerType() {
        return typeParser.getContainerType();
    }


    public int getConstructorIndex() {
        return constructorIndex;
    }

    public void setSpecialSqlType(String specialSqlType) {
        this.specialSqlType = specialSqlType;
    }
}
